//
// Created by xiaoma on 1/31/21.
//

#include "Interpreter.h"
#include <iostream>

static HiObject *compare(int opArg, HiObject *lhs, HiObject *rhs) {
    switch (opArg) {
        case ByteCode::LESS:
            return lhs->less(rhs);
        case ByteCode::LESS_EQUAL:
            return lhs->lessEqual(rhs);
        case ByteCode::EQUAL:
            return lhs->equal(rhs);
        case ByteCode::NOT_EQUAL:
            return lhs->notEqual(rhs);
        case ByteCode::GREATER:
            return lhs->greater(rhs);
        case ByteCode::GREATER_EQUAL:
            return lhs->greaterEqual(rhs);
        default:
            std::cout << "invalid compare code" << std::endl;
            return Universe::False;
    }
}

void Interpreter::run(const CodeObject *codes) {
    int pc = 0;
    int len = codes->getByteCode()->length();
    _stack = new ArrayList<HiObject *>(codes->getStackSize());
    _consts = codes->getConsts();
    auto names = codes->getNames();
    auto locals = new Map<HiObject *, HiObject *>();
    auto block = new ArrayList<HiObject*>();
    while (pc < len) {
        unsigned char opCode = codes->getByteCode()->value()[pc++];
        bool hasArg = (opCode & 0xff) >= ByteCode::HAVE_ARGUMENT;

        int opArg = -1;
        if (hasArg) {
            int byte1 = codes->getByteCode()->value()[pc++] & 0xff;
            opArg = (codes->getByteCode()->value()[pc++] & 0xff) | byte1;
        }

        HiObject *lhs, *rhs, *v, *w;

        switch (opCode) {
            case ByteCode::LOAD_CONST:
                v = _consts->get(opArg);
                _stack->add(v);
                break;
            case ByteCode::STORE_NAME:
                v = names->get(opArg);
                w = POP();
                locals->put(v, w);
                break;
            case ByteCode::LOAD_NAME:
                v = names->get(opArg);
                w = locals->getValue(v);
                if (w==Universe::None) {
                    std::cout<<"FAILED: load name"<<std::endl;
                    return;
                }
                PUSH(w);
                break;
            case ByteCode::PRINT_ITEM:
                v = _stack->pop();
                v->print();
                break;
            case ByteCode::PRINT_NEWLINE:
                std::cout << std::endl;
                break;
            case ByteCode::BINARY_ADD:
                rhs = _stack->pop();
                lhs = _stack->pop();
                _stack->add(lhs->add(rhs));
                break;
            case ByteCode::RETURN_VALUE:
                _stack->pop();
                break;
            case ByteCode::COMPARE_OP:
                rhs = POP();
                lhs = POP();
                v = compare(opArg, lhs, rhs);
                PUSH(v);
                break;
            case ByteCode::POP_JUMP_IF_FALSE:
                v = POP();
                if (v == Universe::False) {
                    pc = opArg;
                }
                break;
            case ByteCode::JUMP_FORWARD:
                pc += opArg;
                break;
            case ByteCode::JUMP_ABSOLUTE:
                pc = opArg;
                break;
            case ByteCode::SETUP_LOOP:
                block->add(new HiInteger(pc+opArg));
                break;
            case ByteCode::BREAK_LOOP:
                v = block->pop();
                pc = ((HiInteger*)v)->value();
                break;
            case ByteCode::POP_BLOCK:
                block->pop();
                break;
            default:
                std::cout << "invalid byte code: " << opCode << std::endl;
        }
    }
}